package org.changs.servlet;

import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;

import com.google.gson.JsonObject;

import org.changs.aplug.utils.FileUtils;
import org.changs.aplug.utils.JsonUtils;
import org.changs.servlet.socket.CoolSocket;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeoutException;

/**
 * Created by yincs on 2017/8/28.
 */

public abstract class AplugServlet {

    private static final int DEFAULT_TIMEOUT = 10000;

    private int mDefaultTimeOut = DEFAULT_TIMEOUT;

    private Thread mServerThread;
    private ServerSocket mServerSocket;
    private SocketAddress mSocketAddress;
    private boolean mIsRunning;
    private Executor mExecutor = Executors.newCachedThreadPool();

    public AplugServlet(int port) {
        this(new InetSocketAddress(port));
    }

    public AplugServlet(InetSocketAddress inetSocketAddress) {
        mSocketAddress = inetSocketAddress;
        try {
            mServerSocket = new ServerSocket();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean start() {
        if (mIsRunning) {
            return true;
        }
        if (mServerSocket == null) {
            //ServerSocket启动失败
            return false;
        }
        if (mSocketAddress == null) {
            //SocketAddress不能为null
            return false;
        }
        try {
            mServerSocket.bind(mSocketAddress);
        } catch (IOException e) {
            e.printStackTrace();
            //监听SocketAddress失败
            return false;
        }
        if (this.mServerThread != null && this.mServerThread.isAlive()) {
            //mServerThread.isAlive()
            return true;
        }
        if (this.mServerThread == null
                || Thread.State.TERMINATED.equals(mServerThread.getState())) {
            this.mServerThread = new Thread(new SocketRunnable());
            this.mServerThread.setDaemon(true);
            this.mServerThread.setName("AplugServlet");
        }
        this.mServerThread.start();
        mIsRunning = true;
        return true;
    }

    public boolean stop() {
        if (mServerThread != null && !mServerThread.isInterrupted())
            mServerThread.interrupt();
        if (mServerSocket != null) {
            if (!mServerSocket.isClosed()) {
                try {
                    mServerSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        mIsRunning = false;
        return true;
    }

    private boolean respondRequest(final Socket socket) {
        mExecutor.execute(new Dispatcher(socket));
        return true;
    }

    public static JsonObject defaultHandleRequest(Context context, JsonObject request) {
        final String action = request.get(IOUtils.EXTRA_ACTION).getAsString();
        String data = null;
        if (request.has(IOUtils.EXTRA_DATA)) {
            data = request.get(IOUtils.EXTRA_DATA).getAsString();
        }
        Intent intent = new Intent(action);
        if (data != null) intent.putExtra(IOUtils.EXTRA_DATA, data);
        intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
        context.sendBroadcast(intent);

        final JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty(IOUtils.EXTRA_DATA, IOUtils.OK);
        return jsonObject;
    }

    public static HandleSendFile defaultHandleSendFile(JsonObject request) {
        String filePath = request.get(IOUtils.EXTRA_FILE_REMOTE_PATH).getAsString();
        final File file = new File(filePath);
        if (FileUtils.isFile(file)) {
            return HandleSendFile.accept(file);
        }
        return HandleSendFile.reject("文件不存在");
    }

    public static HandleReceiveFile defaultHandleReceiveFile(File rootFiel, JsonObject request) {
        if (request.has(IOUtils.EXTRA_FILE_RELATIVE_PATH)) {
            String relativePath = request.get(IOUtils.EXTRA_FILE_RELATIVE_PATH).getAsString();
            return HandleReceiveFile.accept(new File(rootFiel, relativePath));
        }
        return HandleReceiveFile.reject("末指明存储路径");
    }


    private class SocketRunnable implements Runnable {

        @Override
        public void run() {
            while (mIsRunning) {
                try {
                    Socket request = mServerSocket.accept();
                    if (mIsRunning) {
                        respondRequest(request);
                    } else {
                        request.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 处理服务器接收消息分发器
     */
    private class Dispatcher implements Runnable {
        final Socket socket;
        private byte[] buffer = new byte[CommunicationConfig.DEFAULT_BUFFER_SIZE.length];
        private int notifyDelay = 200;

        private Dispatcher(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try {
                socket.setSoTimeout(mDefaultTimeOut);
                String message = IOUtils.readStreamMessage(socket.getInputStream(), mDefaultTimeOut);
                PrintWriter writer = IOUtils.getStreamWriter(socket.getOutputStream());
                if (message.equals(IOUtils.SEND_FILE_TYPE)) {//客户端Send文件\服务器接收操作把文件存到本地
                    receiveFile(writer);
                } else if (message.equals(IOUtils.RECEIVE_FILE_TYPE)) {//客户端下载文件\服务器把该文件发送给客户端
                    sendFile(writer);
                } else {
                    System.out.println("message = " + message);
                    String response;
                    try {
                        JsonObject request = JsonUtils.parseObject(message, JsonObject.class);
                        response = handleRequest(request).toString();
                    } catch (Exception e) {
                        e.printStackTrace();
                        JsonObject jsonObject = new JsonObject();
                        jsonObject.addProperty("error", e.getMessage());
                        jsonObject.addProperty("request", message);
                        response = jsonObject.toString();
                    }
                    writer.write(response);
                    writer.append(CoolSocket.END_SEQUENCE);
                    writer.flush();
                }

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        private void receiveFile(PrintWriter writer) throws IOException, TimeoutException {
            writer.write(IOUtils.OK);
            writer.write(IOUtils.END_SEQUENCE);
            writer.flush();

            String message2 = IOUtils.readStreamMessage(socket.getInputStream(), mDefaultTimeOut);
            final JsonObject jsonObject = JsonUtils.parseObject(message2, JsonObject.class);
            final long fileSize = jsonObject.get(IOUtils.EXTRA_FILE_SIZE).getAsLong();
//            final String fileName = jsonObject.get(IOUtils.EXTRA_FILE_NAME).getAsString();
//            final String fileType = jsonObject.get(IOUtils.EXTRA_FILE_TYPE).getAsString();
            final HandleReceiveFile handleReceiveFile = handleReceiveFile(jsonObject);
            if (handleReceiveFile.reject) {//拒绝接收
                writer.write(handleReceiveFile.rejectMsg);
                writer.write(IOUtils.END_SEQUENCE);
                writer.flush();
                return;
            }
            try {
                writer.write(IOUtils.OK);
                writer.write(IOUtils.END_SEQUENCE);
                writer.flush();
                //开始传文件
                InputStream inputStream = socket.getInputStream();
                File file = handleReceiveFile.file;

                FileUtils.createOrExistsFile(file);
                boolean append = handleReceiveFile.fileAppend;
                System.out.println("file.getAbsolutePath() = " + file.getAbsolutePath());
                FileOutputStream outputStream = new FileOutputStream(file, append);

                handleReceiveFile.getCallback().onStart();
                int len;
                int progressPercent = -1;
                long lastRead = System.currentTimeMillis();
                long lastNotified = System.currentTimeMillis();

                while (file.length() != fileSize) {
                    if ((len = inputStream.read(buffer)) > 0) {
                        outputStream.write(buffer, 0, len);
                        outputStream.flush();
                        lastRead = System.currentTimeMillis();
                    }

                    if ((System.currentTimeMillis() - lastNotified) > notifyDelay) {
                        int currentPercent = (int) (((float) 100 / fileSize) * outputStream.getChannel().position());
                        if (currentPercent > progressPercent) {
                            progressPercent = currentPercent;
                            handleReceiveFile.getCallback().onProgress(progressPercent);
                        }
                        lastNotified = System.currentTimeMillis();
                    }
                    if (handleReceiveFile.isInterrupted)
                        break;
                }

                handleReceiveFile.getCallback().onSuccess();
                outputStream.close();
                inputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                handleReceiveFile.getCallback().onComplete();
            }
        }

        private void sendFile(PrintWriter writer) throws IOException, TimeoutException {
            writer.write(IOUtils.OK);
            writer.write(IOUtils.END_SEQUENCE);
            writer.flush();

            String message2 = IOUtils.readStreamMessage(socket.getInputStream(), mDefaultTimeOut);
//                    System.out.println("message2 = " + message2);
            final JsonObject jsonObject = JsonUtils.parseObject(message2, JsonObject.class);
//            final String fileName = jsonObject.get(IOUtils.EXTRA_FILE_NAME).getAsString();
//            final String fileType = jsonObject.get(IOUtils.EXTRA_FILE_TYPE).getAsString();
            HandleSendFile handleSendFile = handleSendFile(jsonObject);
            if (!handleSendFile.send) {
                writer.write(handleSendFile.msg);
                writer.write(IOUtils.END_SEQUENCE);
                writer.flush();
                return;
            }
            try {
                writer.write(IOUtils.OK);
                writer.write(IOUtils.END_SEQUENCE);
                writer.flush();
                //开始发送文件
                File file = handleSendFile.file;
                System.out.println("file.getAbsolutePath() = " + file.getAbsolutePath());
                FileInputStream inputStream = new FileInputStream(file);
                OutputStream outputStream = socket.getOutputStream();

                handleSendFile.getCallback().onStart();
                int len;
                int progressPercent = -1;
                long lastNotified = System.currentTimeMillis();

                while ((len = inputStream.read(buffer)) > 0) {
                    outputStream.write(buffer, 0, len);
                    outputStream.flush();

                    if ((System.currentTimeMillis() - lastNotified) > notifyDelay) {
                        int currentPercent = (int) (((float) 100 / file.length()) * inputStream.getChannel().position());

                        if (currentPercent > progressPercent) {
                            progressPercent = currentPercent;
                            handleSendFile.getCallback().onProgress(progressPercent);
                        }

                        lastNotified = System.currentTimeMillis();
                    }

                    if (handleSendFile.isInterrupted)
                        break;
                }

                handleSendFile.getCallback().onSuccess();
                outputStream.close();
                inputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                handleSendFile.getCallback().onComplete();
            }
        }
    }


    @NonNull
    protected abstract JsonObject handleRequest(JsonObject request);

    @NonNull
    protected abstract HandleReceiveFile handleReceiveFile(JsonObject request);

    @NonNull
    protected abstract HandleSendFile handleSendFile(JsonObject request);

}
